
import org.json.simple.JSONObject;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    private static Socket socket;
    public static boolean status = false;
    private static ObjectInputStream ois;
    private static ObjectOutputStream oos;

    /**
     * 为什么要使用静态方法？
     *
     * 静态方法的好处就是不用生成类的实例就可以直接调用。
     * static方法修饰的成员不再属于某个对象，而是属于它所在的类。只需要通过其类名就可以访问，不需要再消耗资源反复创建对象。
     * 在类第一次加载的时候，static就已经在内存中了，直到程序结束后，该内存才会释放。
     * 如果不是static修饰的成员函数，在使用完之后就会立即被JVM回收。
     *
     * 什么时候使用static？
     *
     * 如果这个方法是作为一个工具来使用的，就声明为static，不需要new一个对象就可以使用。比如：connect DB就可以声明一个Connection()的static方法，
     * 因为是静态的，说明connection DB不是某个对象所特有的功能，只是作为一种连接到DB的工具。
     */

    public static void main(String[] args) {
        while (!status){
            System.out.println("正在尝试连接...");
            connect();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 连接
     */
    private static void connect(){
        try {
            socket = new Socket("127.0.0.1",9999);
            status = true;

            //获取对象输入流
            //getInputStream方法可以得到一个输入流，客户端的Socket对象上的getInputStream方法得到输入流其实就是从服务器端发回的数据
            ois = new ObjectInputStream(socket.getInputStream());

            //获取对象输出流
            oos = new ObjectOutputStream(socket.getOutputStream());
            //getOutputStream方法得到的是一个输出流，客户端的Socket对象上的getOutputStream方法得到的输出流其实就是发送给服务器端的数据

            //客户端监听线程
            Thread threadClientListen = new Thread(new ClientListen(socket,ois));
            threadClientListen.start();

            //客户端发送线程
            Thread threadClientSend = new Thread(new ClientSend(socket,oos));
            threadClientSend.start();

            //客户端心跳线程
            Thread threadClientHeart = new Thread(new ClientHeart(socket,oos));
            threadClientHeart.start();
        } catch (IOException e) {
            status = false;
            e.printStackTrace();
        }

    }

    /**
     * 重新连接
     */
    public static void reconnect(){
        while (!status){
            System.out.println("正在尝试重新连接...");
            //调用连接
            connect();
            try {
                //每3秒重新连接
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

/**
 * 客户端监听线程
 */
class ClientListen implements Runnable{

    private Socket socket;
    private ObjectInputStream ois;

    public ClientListen(Socket socket,ObjectInputStream ois) {
        this.socket = socket;
        this.ois = ois;
    }

    @Override
    public void run() {
        try{
            while(true){//while循环保持监听状态
                System.out.println(ois.readObject());
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

/**
 * 客户端发送线程
 */
class ClientSend implements Runnable{

    private Socket socket;
    private ObjectOutputStream oos;

    public ClientSend(Socket socket,ObjectOutputStream oos) {
        this.socket = socket;
        this.oos = oos;
    }

    @Override
    public void run() {
        try {
            Scanner scanner = new Scanner(System.in);
            while(true){
                System.out.println("请输入要发送的内容...");
                String string = scanner.nextLine();
                //https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple
                JSONObject object = new JSONObject();
                object.put("To server",socket.getInetAddress()+":"+socket.getPort());
                object.put("type","chat");
                object.put("msg",string );
                oos.writeObject(object);
                oos.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
            try {
                socket.close();
                Client.status = false;
                Client.reconnect();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }
    }
}

/**
 * 客户端心跳包
 */
class ClientHeart implements Runnable{

    private Socket socket;
    private ObjectOutputStream oos;

    public ClientHeart(Socket socket,ObjectOutputStream oos) {
        this.socket = socket;
        this.oos = oos;
    }

    @Override
    public void run() {
        try {
            System.out.println("心跳包线程已启动...");
            while(true){
                Thread.sleep(5000);
                JSONObject object = new JSONObject();
                object.put("To server",socket.getInetAddress()+":"+socket.getPort());
                object.put("type","heart");
                object.put("msg","1");
                oos.writeObject(object);
                oos.flush();
            }
        } catch (Exception e) {
            try {
                socket.close();
                Client.status = false;
                Client.reconnect();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }
    }
}